#pragma once

#include <atlsafe.h>

namespace Automation {
namespace IE {

/////////////////////////////////////////////////////////////////////////////////////////////////////////
// macroses to create properties

#define PROPERTY_READONLY_BSTR(name) \
	__declspec(property(get = get_##name)) CComBSTR name;\
	CComBSTR get_##name() const { ATLASSERT(p != NULL); CComBSTR name; if (!p || FAILED(p->get_##name(&name))) return CComBSTR(); return name; }

#define PROPERTY_READONLY_BSTR_EX(ptr, name) \
	__declspec(property(get = get_##name)) CComBSTR name;\
	CComBSTR get_##name() const { ATLASSERT(p != NULL); CComBSTR name; if (!ptr || FAILED(ptr->get_##name(&name))) return CComBSTR(); return name; }

#define PROPERTY_WRITEONLY_BSTR(name) \
	__declspec(property(put = put_##name)) CComBSTR name;\
	bool put_##name(const CComBSTR &name) const { ATLASSERT(p != NULL); return p && SUCCEEDED(p->put_##name(name)); }\
	bool put_##name(LPCSTR pszName) const { ATLASSERT(p != NULL); return p && SUCCEEDED(p->put_##name(CComBSTR(pszName))); }\
	bool put_##name(LPCWSTR pszName) const { ATLASSERT(p != NULL); return p && SUCCEEDED(p->put_##name(CComBSTR(pszName))); }

#define PROPERTY_WRITEONLY_BSTR_EX(ptr, name) \
	__declspec(property(put = put_##name)) CComBSTR name;\
	bool put_##name(const CComBSTR &name) const { ATLASSERT(p != NULL); return ptr && SUCCEEDED(ptr->put_##name(name)); }\
	bool put_##name(LPCSTR pszName) const { ATLASSERT(p != NULL); return ptr && SUCCEEDED(ptr->put_##name(CComBSTR(pszName))); }\
	bool put_##name(LPCWSTR pszName) const { ATLASSERT(p != NULL); return ptr && SUCCEEDED(ptr->put_##name(CComBSTR(pszName))); }

#define PROPERTY_BSTR(name) \
	__declspec(property(get = get_##name, put = put_##name)) CComBSTR name;\
	CComBSTR get_##name() const { ATLASSERT(p != NULL); CComBSTR name; if (!p || FAILED(p->get_##name(&name))) return CComBSTR(); return name; }\
	bool put_##name(const CComBSTR &name) const { ATLASSERT(p != NULL); return p && SUCCEEDED(p->put_##name(name)); }\
	bool put_##name(LPCSTR pszName) const { ATLASSERT(p != NULL); return p && SUCCEEDED(p->put_##name(CComBSTR(pszName))); }\
	bool put_##name(LPCWSTR pszName) const { ATLASSERT(p != NULL); return p && SUCCEEDED(p->put_##name(CComBSTR(pszName))); }

#define PROPERTY_BSTR_EX(ptr, name) \
	__declspec(property(get = get_##name, put = put_##name)) CComBSTR name;\
	CComBSTR get_##name() const { ATLASSERT(p != NULL); CComBSTR name; if (!ptr || FAILED(ptr->get_##name(&name))) return CComBSTR(); return name; }\
	bool put_##name(const CComBSTR &name) const { ATLASSERT(ptr != NULL); return ptr && SUCCEEDED(ptr->put_##name(name)); } \
	bool put_##name(LPCSTR pszName) const { ATLASSERT(ptr != NULL); return ptr && SUCCEEDED(ptr->put_##name(CComBSTR(pszName))); }\
	bool put_##name(LPCWSTR pszName) const { ATLASSERT(ptr != NULL); return ptr && SUCCEEDED(ptr->put_##name(CComBSTR(pszName))); }

#define PROPERTY_BSTR_VARIANT(name) \
	__declspec(property(get = get_##name, put = put_##name)) CComBSTR name;\
	CComBSTR get_##name() const { ATLASSERT(p != NULL); CComVariant name; if (!p || FAILED(p->get_##name(&name))) return CComBSTR(); return (VT_BSTR == name.vt) ? name.bstrVal : CComBSTR(); }\
	bool put_##name(const CComBSTR &name) const { ATLASSERT(p != NULL); return p && SUCCEEDED(p->put_##name(CComVariant(name))); }\
	bool put_##name(LPCSTR pszName) const { ATLASSERT(p != NULL); return p && SUCCEEDED(p->put_##name(CComVariant(pszName))); }\
	bool put_##name(LPCWSTR pszName) const { ATLASSERT(p != NULL); return p && SUCCEEDED(p->put_##name(CComVariant(pszName))); }

#define PROPERTY_BSTR_VARIANT_EX(ptr, name) \
	__declspec(property(get = get_##name, put = put_##name)) CComBSTR name;\
	CComBSTR get_##name() const { ATLASSERT(p != NULL); CComVariant name; if (!ptr || FAILED(ptr->get_##name(&name))) return CComBSTR(); return (VT_BSTR == name.vt) ? name.bstrVal : CComBSTR(); }\
	bool put_##name(const CComBSTR &name) const { ATLASSERT(p != NULL); return ptr && SUCCEEDED(ptr->put_##name(CComVariant(name))); }\
	bool put_##name(LPCSTR pszName) const { ATLASSERT(p != NULL); return ptr && SUCCEEDED(ptr->put_##name(CComVariant(pszName))); }\
	bool put_##name(LPCWSTR pszName) const { ATLASSERT(p != NULL); return ptr && SUCCEEDED(ptr->put_##name(CComVariant(pszName))); }

#define PROPERTY_READONLY(type, name) \
	__declspec(property(get = get_##name)) type name;\
	type get_##name() const { ATLASSERT(p != NULL); type name; if (!p || FAILED(p->get_##name(&name))) return type(); return name; }\

#define PROPERTY_WRITEONLY(type, name) \
	__declspec(property(put = put_##name)) type name;\
	bool put_##name(const type &name) const { ATLASSERT(p != NULL); return p && SUCCEEDED(p->put_##name(name)); }

#define PROPERTY(type, name) \
	__declspec(property(get = get_##name, put = put_##name)) type name;\
	type get_##name() const { ATLASSERT(p != NULL); type name; if (!p || FAILED(p->get_##name(&name))) return type(); return name; }\
	bool put_##name(const type &name) const { ATLASSERT(p != NULL); return p && SUCCEEDED(p->put_##name(name)); }

#define PROPERTY_READONLY_EX(ptr, type, name) \
	__declspec(property(get = get_##name)) type name;\
	type get_##name() const { ATLASSERT(ptr != NULL); type name; if (!ptr || FAILED(ptr->get_##name(&name))) return type(); return name; }

#define PROPERTY_WRITEONLY_EX(ptr, type, name) \
	__declspec(property(put = put_##name)) type name;\
	bool put_##name(const type &name) const { ATLASSERT(ptr != NULL); return ptr && SUCCEEDED(ptr->put_##name(name)); }

#define PROPERTY_EX(ptr, type, name) \
	__declspec(property(get = get_##name, put = put_##name)) type name;\
	type get_##name() const { ATLASSERT(ptr != NULL); type name; if (!ptr || FAILED(ptr->get_##name(&name))) return type(); return name; }\
	bool put_##name(const type &name) const { ATLASSERT(ptr != NULL); return ptr && SUCCEEDED(ptr->put_##name(name)); }

#define PROPERTY_JSEVENT(name) \
	__declspec(property(get = get_##name, put = put_##name)) CComPtr<IDispatch> name;\
	CComPtr<IDispatch> get_##name() const { ATLASSERT(p != NULL); CComVariant name; if (!p || FAILED(p->get_##name(&name))) return CComPtr<IDispatch>(); return (VT_DISPATCH == name.vt) ? CComPtr<IDispatch>(name.pdispVal) : CComPtr<IDispatch>(); }\
	bool put_##name(const CComPtr<IDispatch> &name) const { ATLASSERT(p != NULL); if (!p) return false; return SUCCEEDED(!name ? p->put_##name(CComVariant()) : p->put_##name(CComVariant(name))); }

#define PROPERTY_JSEVENT_EX(ptr, name) \
	__declspec(property(get = get_##name, put = put_##name)) CComPtr<IDispatch> name;\
	CComPtr<IDispatch> get_##name() const { ATLASSERT(ptr != NULL); CComVariant name; if (!ptr || FAILED(ptr->get_##name(&name))) return CComPtr<IDispatch>(); return (VT_DISPATCH == name.vt) ? CComPtr<IDispatch>(name.pdispVal) : CComPtr<IDispatch>(); }\
	bool put_##name(const CComPtr<IDispatch> &name) const { ATLASSERT(ptr != NULL); if (!ptr) return false; return SUCCEEDED(!name ? ptr->put_##name(CComVariant()) : ptr->put_##name(CComVariant(name))); }

#define PROPERTY_READONLY_VARIANT(name) \
	__declspec(property(get = get_##name)) CComVariant name;\
	CComVariant get_##name() const { ATLASSERT(p != NULL); CComVariant name; if (!p || FAILED(p->get_##name(&name))) return CComVariant(); return name; }

#define PROPERTY_WRITEONLY_VARIANT(name) \
	__declspec(property(put = put_##name)) CComVariant name;\
	CComVariant put_##name(const CComVariant &name) const { ATLASSERT(p != NULL); return p ? SUCCEEDED(p->put_##name(name)) : false; }

#define PROPERTY_VARIANT(name) \
	__declspec(property(get = get_##name, put = put_##name)) CComVariant name;\
	CComVariant get_##name() const { ATLASSERT(p != NULL); CComVariant name; if (!p || FAILED(p->get_##name(&name))) return CComVariant(); return name; }\
	CComVariant put_##name(const CComVariant &name) const { ATLASSERT(p != NULL); return p ? SUCCEEDED(p->put_##name(name)) : false; }

#define PROPERTY_READONLY_bool(name) \
	__declspec(property(get = get_##name)) bool name;\
	bool get_##name() const { ATLASSERT(p != NULL); VARIANT_BOOL name = VARIANT_FALSE; if (!p || FAILED(p->get_##name(&name))) return false; return VARIANT_TRUE == name; }

#define PROPERTY_READONLY_bool_EX(ptr, name) \
	__declspec(property(get = get_##name)) bool name;\
	bool get_##name() const { ATLASSERT(p != NULL); VARIANT_BOOL name = VARIANT_FALSE; if (!ptr || FAILED(ptr->get_##name(&name))) return false; return VARIANT_TRUE == name; }

#define PROPERTY_WRITEONLY_bool(name) \
	__declspec(property(put = put_##name)) bool name;\
	bool put_##name(bool name) const { ATLASSERT(p != NULL); return p ? SUCCEEDED(p->put_##name(name ? VARIANT_TRUE : VARIANT_FALSE)) : false; }

#define PROPERTY_bool(name) \
	__declspec(property(get = get_##name, put = put_##name)) bool name;\
	bool get_##name() const { ATLASSERT(p != NULL); VARIANT_BOOL name = VARIANT_FALSE; if (!p || FAILED(p->get_##name(&name))) return false; return VARIANT_TRUE == name; }\
	bool put_##name(bool name) const { ATLASSERT(p != NULL); return p ? SUCCEEDED(p->put_##name(name ? VARIANT_TRUE : VARIANT_FALSE)) : false; }

#define PROPERTY_READONLY_long(name) \
	__declspec(property(get = get_##name)) long name;\
	long get_##name() const { ATLASSERT(p != NULL); long name = 0; if (!p || FAILED(p->get_##name(&name))) return 0; return name; }

#define PROPERTY_READONLY_long_EX(ptr, name) \
	__declspec(property(get = get_##name)) long name;\
	long get_##name() const { ATLASSERT(p != NULL); long name = 0; if (!ptr || FAILED(ptr->get_##name(&name))) return 0; return name; }

#define PROPERTY_WRITEONLY_long(name) \
	__declspec(property(put = put_##name)) long name;\
	bool put_##name(long name) const { ATLASSERT(p != NULL); return p ? SUCCEEDED(p->put_##name(name)) : false; }

#define PROPERTY_WRITEONLY_long_EX(ptr, name) \
	__declspec(property(put = put_##name)) long name;\
	bool put_##name(long name) const { ATLASSERT(p != NULL); return ptr ? SUCCEEDED(ptr->put_##name(name)) : false; }

#define PROPERTY_long(name) \
	__declspec(property(get = get_##name, put = put_##name)) long name;\
	long get_##name() const { ATLASSERT(p != NULL); long name = 0; if (!p || FAILED(p->get_##name(&name))) return 0; return name; }\
	bool put_##name(long name) const { ATLASSERT(p != NULL); return p ? SUCCEEDED(p->put_##name(name)) : false; }

#define PROPERTY_long_EX(ptr, name) \
	__declspec(property(get = get_##name, put = put_##name)) long name;\
	long get_##name() const { ATLASSERT(ptr != NULL); long name = 0; if (!ptr || FAILED(ptr->get_##name(&name))) return 0; return name; }\
	bool put_##name(long name) const { ATLASSERT(ptr != NULL); return ptr ? SUCCEEDED(ptr->put_##name(name)) : false; }

#define PROPERTY_READONLY_int(name) \
	__declspec(property(get = get_##name)) int name;\
	int get_##name() const { ATLASSERT(p != NULL); int name = 0; if (!p || FAILED(p->get_##name(&name))) return 0; return name; }

#define PROPERTY_WRITEONLY_int(name) \
	__declspec(property(put = put_##name)) int name;\
	bool put_##name(int name) const { ATLASSERT(p != NULL); return p ? SUCCEEDED(p->put_##name(name)) : false; }

#define PROPERTY_int(name) \
	__declspec(property(get = get_##name, put = put_##name)) int name;\
	int get_##name() const { ATLASSERT(p != NULL); int name = 0; if (!p || FAILED(p->get_##name(&name))) return 0; return name; }\
	bool put_##name(int name) const { ATLASSERT(p != NULL); return p ? SUCCEEDED(p->put_##name(name)) : false; }

#define PROPERTY_READONLY_PTR(type, name) \
	__declspec(property(get = get_##name)) CComPtr<type> name;\
	CComPtr<type> get_##name() const { ATLASSERT(p != NULL); CComPtr<type> name; if (!p || FAILED(p->get_##name(&name))) return CComPtr<type>(); return name; }\

#define PROPERTY_WRITEONLY_PTR(type, name) \
	__declspec(property(put = put_##name)) CComPtr<type> name;\
	bool put_##name(const CComPtr<type> &name) const { ATLASSERT(p != NULL); return p && SUCCEEDED(p->put_##name(name)); }

#define PROPERTY_PTR(type, name) \
	__declspec(property(get = get_##name, put = put_##name)) CComPtr<type> name;\
	CComPtr<type> get_##name() const { ATLASSERT(p != NULL); CComPtr<type> name; if (!p || FAILED(p->get_##name(&name))) return CComPtr<type>(); return name; }\
	bool put_##name(const CComPtr<type> &name) const { ATLASSERT(p != NULL); return p && SUCCEEDED(p->put_##name(name)); }

#define INTERFACE_PROPERTY(type, name) \
	mutable CComPtr<type> m_##name; \
	__declspec(property(get = Get##name)) CComPtr<type> name;\
	CComPtr<type> &Get##name() const { ATLASSERT(p != NULL); if (!m_##name && p) p->QueryInterface(&m_##name); return m_##name; }

/////////////////////////////////////////////////////////////////////////////////////////////////////////
// Wrappers
namespace JS {

    class Method : public CComPtr<IDispatch>
    {
    private:
        CComVariant m_vtResult;

    public:
        Method(const CComPtr<IDispatch> &pDispatch)
            : CComPtr<IDispatch>(pDispatch)
        {
        }

        HRESULT operator () ()
        {
            return DispCall();
        }

        template <typename A1>
        HRESULT operator () (const A1 &rParam1)
        {
            ATLASSERT(p);

            CComVariant params[1];
		    params[0] = rParam1;

            return DispCall(params, sizeof(params)/sizeof(CComVariant));
        }

        template <typename A1, typename A2>
        HRESULT operator () (const A1 &rParam1, const A2 &rParam2)
        {
            ATLASSERT(p);

            CComVariant params[2];
		    params[1] = rParam1;
		    params[0] = rParam2;

            return DispCall(params, sizeof(params)/sizeof(CComVariant));
        }

        template <typename A1, typename A2, typename A3>
        HRESULT operator () (const A1 &rParam1, const A2 &rParam2, const A3 &rParam3)
        {
            ATLASSERT(p);

            CComVariant params[3];
		    params[2] = rParam1;
		    params[1] = rParam2;
		    params[0] = rParam3;

            return DispCall(params, sizeof(params)/sizeof(CComVariant));
        }

        const CComVariant &GetResult() const
        {
            return m_vtResult;
        }

    private:
        HRESULT DispCall(VARIANT* pParams = NULL, UINT nArgCount = 0)
        {
            if (!p)
                return E_FAIL;

            m_vtResult.Clear();

		    DISPPARAMS dispparams = {pParams, NULL, nArgCount, 0};
            return p->Invoke(0,IID_NULL,LOCALE_SYSTEM_DEFAULT,DISPATCH_METHOD,&dispparams,&m_vtResult,NULL,NULL);
        }
    };

};//namespace JS

namespace HTML {

/**
 * IHTMLStyle wrapper
 */
class Style : public CComPtr<IHTMLStyle>
{
private:
	INTERFACE_PROPERTY(IHTMLStyle2, Style2)
	INTERFACE_PROPERTY(IHTMLStyle4, Style4)
	INTERFACE_PROPERTY(IHTMLStyle5, Style5)

public:
	Style()
	{
	}

	Style(IHTMLStyle* pHTMLStyle)
		: CComPtr<IHTMLStyle>(pHTMLStyle)
	{
	}

    PROPERTY_BSTR(display)
	PROPERTY_BSTR_VARIANT(height)
	PROPERTY_BSTR_VARIANT(width)
	PROPERTY_BSTR(border)
	PROPERTY_BSTR(padding)
	PROPERTY_BSTR_VARIANT(paddingLeft)
	PROPERTY_BSTR_VARIANT(paddingRight)
	PROPERTY_BSTR_VARIANT(paddingTop)
	PROPERTY_BSTR_VARIANT(paddingBottom)
	PROPERTY_BSTR(margin)
	PROPERTY_BSTR_VARIANT(marginBottom)
	PROPERTY_BSTR_VARIANT(marginLeft)
	PROPERTY_BSTR_VARIANT(marginRight)
	PROPERTY_BSTR_VARIANT(marginTop)
	PROPERTY_BSTR(visibility)
	PROPERTY_BSTR(styleFloat)
	PROPERTY_BSTR(clear)
 	PROPERTY_BSTR_EX(Style2, overflowX)
 	PROPERTY_BSTR_EX(Style2, overflowY)
	PROPERTY_BSTR_EX(Style2, position)
	PROPERTY_BSTR_VARIANT_EX(Style4, minHeight)
	PROPERTY_BSTR_VARIANT_EX(Style5, maxHeight)
	PROPERTY_BSTR_VARIANT_EX(Style5, minWidth)
	PROPERTY_BSTR_VARIANT_EX(Style5, maxWidth)
};

/**
 * IHTMLDataTransfer wrapper
 */
class DataTransfer : public CComPtr<IHTMLDataTransfer>
{
public:
	DataTransfer()
	{
	}

	DataTransfer(IHTMLDataTransfer* pDataTransfer)
		: CComPtr<IHTMLDataTransfer>(pDataTransfer)
	{
	}

	PROPERTY_BSTR(dropEffect)
	PROPERTY_BSTR(effectAllowed)
	__declspec(property(get = GetDataObject)) CComPtr<IDataObject> data;

	bool clearData(const CComBSTR &format)
	{
		ATLASSERT(p != NULL);

		VARIANT_BOOL vbValue = VARIANT_FALSE;
		if (!p || FAILED(p->clearData(format, &vbValue)))
			return false;

		return VARIANT_TRUE == vbValue;
	}

	CComPtr<IDataObject> GetDataObject()
	{
		ATLASSERT(p != NULL);

		CComPtr<IServiceProvider> provider;
		if (FAILED(QueryInterface(&provider)) || !provider)
			return CComPtr<IDataObject>();

		CComPtr<IDataObject> data;
		if (FAILED(provider->QueryService(IID_IDataObject, __uuidof(IDataObject), (void**)&data)) || !data)
			return CComPtr<IDataObject>();

		return data;
	}
};

/**
 * IHTMLTxtRange wrapper
 */
class TxtRange : public CComPtr<IHTMLTxtRange>
{
public:
	TxtRange()
	{
	}

	TxtRange(IHTMLTxtRange* pTxtRange)
		: CComPtr<IHTMLTxtRange>(pTxtRange)
	{
	}

	PROPERTY_BSTR(text)
	PROPERTY_READONLY_BSTR(htmlText)
};

/**
 * IHTMLSelectionObject wrapper
 */
class Selection : public CComPtr<IHTMLSelectionObject>
{
public:
	Selection()
	{
	}

	Selection(IHTMLSelectionObject* pSelection)
		: CComPtr<IHTMLSelectionObject>(pSelection)
	{
	}

	__declspec(property(get = GetTextRange)) TxtRange textRange;

	TxtRange GetTextRange() const
	{
		ATLASSERT(p != NULL);

		CComPtr<IDispatch> dispTextRange;
		if (!p || FAILED(p->createRange(&dispTextRange)) || !dispTextRange)
			return TxtRange();

		TxtRange htmlTextRange;
		if (FAILED(dispTextRange->QueryInterface(&htmlTextRange)))
			return TxtRange();

		return htmlTextRange;
	}
};

/**
 * IHTMLControlRange wrapper
 */
class ControlRange : public CComPtr<IHTMLControlRange>
{
public:
	ControlRange()
	{
	}

	ControlRange(IHTMLControlRange* pRange)
		: CComPtr<IHTMLControlRange>(pRange)
	{
	}

	bool add(const CComPtr<IHTMLControlElement> &item)
	{
		return (!p || !item) ? false : SUCCEEDED(p->add(item));
	}

	bool execCommand(const CComBSTR &cmdID, VARIANT value = CComVariant(), bool showUI = false)
	{
		ATLASSERT(p != NULL);

		VARIANT_BOOL vbResult = VARIANT_FALSE;
		if (!p || FAILED(p->execCommand(cmdID, ToVariant(showUI), value, &vbResult)))
			return false;

		return VARIANT_TRUE == vbResult;
	}
};

/**
 * IHTMLDOMNode template wrapper
 */
template <typename IPrimary = IHTMLDOMNode>
class TDOMNode : public CComPtr<IPrimary>
{
private:
    INTERFACE_PROPERTY(IHTMLDOMNode, Primary)

protected:
    typedef TDOMNode __base_DOMNode;

public:
	TDOMNode()
	{
	}

	TDOMNode(IPrimary* pElement)
		: CComPtr<IPrimary>(pElement)
	{
	}

	TDOMNode(IDispatch* pDispatch)
	{
		if (pDispatch) pDispatch->QueryInterface(&*this);
	}

    template <typename IPrime>
	TDOMNode(const TDOMNode<IPrime> node)
	{
		if (node) node->QueryInterface(&*this);
	}

	operator IDispatch* () const
	{
		return p;
	}

	operator CComPtr<IDispatch> () const
	{
		return p;
	}

	operator IHTMLDOMNode* () const
	{
		return Primary;
	}

    operator bool () const
	{
		return !!p && !!Primary;
	}

    bool operator ! () const
	{
		return !p || !Primary;
	}

	PROPERTY_READONLY_BSTR_EX(Primary, nodeName)
	PROPERTY_READONLY_long_EX(Primary, nodeType)
	PROPERTY_READONLY_EX(Primary, TDOMNode<>, parentNode)
	PROPERTY_READONLY_EX(Primary, TDOMNode<>, firstChild)
	PROPERTY_READONLY_EX(Primary, TDOMNode<>, lastChild)
	PROPERTY_READONLY_EX(Primary, TDOMNode<>, nextSibling)
	PROPERTY_READONLY_EX(Primary, TDOMNode<>, previousSibling)

	bool appendChild(const TDOMNode<> &child)
	{
		if (!Primary || !child)
			return false;

		CComPtr<IHTMLDOMNode> Child;
		return SUCCEEDED(Primary->appendChild(child, &Child)) && Child;
	}

	bool removeChild(const TDOMNode<> &child)
	{
		if (!Primary || !child)
			return false;

		CComPtr<IHTMLDOMNode> Child;
		return SUCCEEDED(Primary->removeChild(child, &Child)) && Child;
	}
};

typedef TDOMNode<> DOMNode;

/**
 * IHTMLElement template wrapper
 */
template <typename IPrimary = IHTMLElement>
class TElement : public TDOMNode<IPrimary>
{
private:
    INTERFACE_PROPERTY(IHTMLElement,  Primary)
	INTERFACE_PROPERTY(IHTMLElement2, Element2)
	INTERFACE_PROPERTY(IHTMLElement3, Element3)

protected:
    typedef TElement __base_Element;

public:
	TElement()
	{
	}

	TElement(IPrimary* pElement)
		: __base_DOMNode(pElement)
	{
	}

	TElement(IDispatch* pDispatch)
		: __base_DOMNode(pDispatch)
	{
	}

    template <typename IPrime>
	TElement(const TElement<IPrime> elem)
	{
		if (elem) elem->QueryInterface(&*this);
	}

	operator IHTMLElement* () const
	{
		return Primary;
	}

	operator IDispatch* () const
	{
		return p;
	}

	operator CComPtr<IDispatch> () const
	{
		return p;
	}

    operator bool () const
	{
		return !!p && !!Primary;
	}

    bool operator ! () const
	{
		return !p || !Primary;
	}

	PROPERTY_BSTR_EX(Primary, id)
	PROPERTY_BSTR_EX(Primary, title)
	PROPERTY_BSTR_EX(Primary, innerText)
	PROPERTY_BSTR_EX(Primary, innerHTML)
	PROPERTY_BSTR_EX(Primary, className)
	PROPERTY_READONLY_BSTR_EX(Primary, tagName)
	PROPERTY_READONLY_bool_EX(Primary, isTextEdit)
	PROPERTY_READONLY_EX(Primary, TElement<>, parentElement)
	PROPERTY_READONLY_EX(Primary, Style, style)
	__declspec(property(get = IsTextArea))		bool				isTextArea;
	__declspec(property(get = IsInputText))		bool				isInputText;
	__declspec(property(get = IsButton))		bool				isButton;
	__declspec(property(get = IsLink))			bool				isLink;
	PROPERTY_JSEVENT_EX(Primary,  onclick)
	PROPERTY_JSEVENT_EX(Primary,  ondblclick)
	PROPERTY_JSEVENT_EX(Element2, ondragenter)
	PROPERTY_JSEVENT_EX(Element2, ondragover)
	PROPERTY_JSEVENT_EX(Element2, ondrop)
	PROPERTY_JSEVENT_EX(Element2, onpropertychange)

	bool IsTextArea() const
	{
		ATLASSERT(p != NULL);

		CComPtr<IHTMLTextAreaElement> textArea;
		if (!p || FAILED(p->QueryInterface(&textArea)) || !textArea)
			return false;

		return true;
	}

	bool IsInputText() const
	{
		ATLASSERT(p != NULL);

		CComPtr<IHTMLInputTextElement> inputText;
		if (!p || FAILED(p->QueryInterface(&inputText)) || !inputText)
			return false;

		return true;
	}

	bool IsButton() const
	{
		ATLASSERT(p != NULL);

		if (!p)
			return false;

		CComPtr<IHTMLInputButtonElement> inputButton;
		if (SUCCEEDED(p->QueryInterface(&inputButton)) && !!inputButton)
			return true;

		CComPtr<IHTMLButtonElement> button;
		if (SUCCEEDED(p->QueryInterface(&button)) && !!button)
			return true;

		return false;
	}

	bool IsLink() const
	{
		ATLASSERT(p != NULL);

		CComPtr<IHTMLAnchorElement> anchor;
		if (!p || FAILED(p->QueryInterface(&anchor)) || !anchor)
			return false;

		return true;
	}

	bool focus() const
	{
		return Element2 ? SUCCEEDED(Element2->focus()) : false;
	}

	bool setActive() const
	{
		return Element3 ? SUCCEEDED(Element3->setActive()) : false;
	}

	void AppendInnerText(const CComBSTR &bsTextToAppend)
	{
		CComBSTR bsInnerText = innerText;

		bsInnerText += bsTextToAppend;

		innerText = bsInnerText;
	}

	void AppendInnerText(const CComBSTR &bsTextToAppend, const CStringW &strSeparator)
	{
		CComBSTR bsInnerText = innerText;

		if (bsInnerText && bsInnerText.Length() > 0)
			bsInnerText += strSeparator;

		bsInnerText += bsTextToAppend;

		innerText = bsInnerText;
	}

	ControlRange createControlRange() const
	{
		if (!Element2)
			return ControlRange();

		CComPtr<IDispatch> dispRange;
		if (FAILED(Element2->createControlRange(&dispRange)) || !dispRange)
			return ControlRange();

		ControlRange range;
		if (FAILED(dispRange->QueryInterface(&range)))
			return ControlRange();

		return range;
	}

	bool attachEvent(const CComBSTR &bsEvent, const CComPtr<IDispatch> &dispEvent)
	{
		if (!Element2)
			return false;

		VARIANT_BOOL vbRes = VARIANT_FALSE;
		if (FAILED(Element2->attachEvent(bsEvent, dispEvent, &vbRes)))
			return false;

		ATLASSERT(VARIANT_TRUE == vbRes);

		return VARIANT_TRUE == vbRes;
	}

	bool detachEvent(const CComBSTR &bsEvent, const CComPtr<IDispatch> &dispEvent)
	{
		return !Element2 ? false : S_OK == Element2->detachEvent(bsEvent, dispEvent);
	}
};

typedef TElement<> Element;

/**
 * IHTMLBodyElement wrapper
 */
class BodyElement : public Element
{
private:
	INTERFACE_PROPERTY(IHTMLBodyElement, Body)

public:
	BodyElement()
	{
	}

	BodyElement(IDispatch* pDispatch)
		: Element(pDispatch)
	{
	}

	PROPERTY_JSEVENT_EX(Body, onload)
	PROPERTY_JSEVENT_EX(Body, onunload)
};

/**
 * IHTMLEventObj wrapper
 */
class Event : public CComPtr<IHTMLEventObj>
{
private:
	INTERFACE_PROPERTY(IHTMLEventObj2, Event2)

public:
	Event()
	{
	}

	Event(IHTMLEventObj* pEvent)
		: CComPtr<IHTMLEventObj>(pEvent)
	{
	}

	PROPERTY_READONLY_bool(altKey)
	PROPERTY_READONLY_bool(ctrlKey)
	PROPERTY_READONLY_bool(shiftKey)
	PROPERTY_READONLY_long(button)
	PROPERTY_bool(cancelBubble);
	PROPERTY_long(keyCode)
	PROPERTY_READONLY(Element, srcElement)
	PROPERTY_READONLY(Element, fromElement)
	PROPERTY_READONLY(Element, toElement)
	PROPERTY_long_EX(Event2, clientX)
	PROPERTY_long_EX(Event2, clientY)
	__declspec(property(get = GetClientPos))	POINT clientPos;
	PROPERTY_long_EX(Event2, screenX)
	PROPERTY_long_EX(Event2, screenY)
	__declspec(property(get = GetScreenPos))	POINT screenPos;
	PROPERTY_READONLY_EX(Event2, DataTransfer, dataTransfer)
	PROPERTY_VARIANT(returnValue);
	PROPERTY_READONLY_BSTR(type);

	POINT GetClientPos() const
	{
		ATLASSERT(p != NULL);

		POINT pos = {0,0};

		if (!GetEvent2())
			return pos;

		GetEvent2()->get_clientX(&pos.x);
		GetEvent2()->get_clientY(&pos.y);

		return pos;
	}

	POINT GetScreenPos() const
	{
		ATLASSERT(p != NULL);

		POINT pos = {0,0};

		if (!GetEvent2())
			return pos;

		GetEvent2()->get_screenX(&pos.x);
		GetEvent2()->get_screenY(&pos.y);

		return pos;
	}
};

/**
 * IHTMLImgElement wrapper
 */
class ImgElement : public TElement<IHTMLImgElement>
{
public:
	ImgElement()
	{
	}

	ImgElement(IHTMLImgElement* pImg)
		: __base_Element(pImg)
	{
	}

	ImgElement(const Element &elem)
		: __base_Element(elem)
	{
	}

	PROPERTY_BSTR(src)
	PROPERTY_READONLY_BSTR(href)
	PROPERTY_READONLY_bool(complete)

	operator CComPtr<IDispatch> () const
	{
		return p;
	}

	operator CComPtr<IHTMLControlElement> () const
	{
		CComPtr<IHTMLControlElement> controlElement;
		if (!p || FAILED(p->QueryInterface(&controlElement)))
			return CComPtr<IHTMLControlElement>();

		return controlElement;
	}
};

/**
 * IHTMLAnchorElement wrapper
 */
class AnchorElement : public TElement<IHTMLAnchorElement>
{
public:
	AnchorElement()
	{
	}

	AnchorElement(IHTMLAnchorElement* pA)
		: __base_Element(pA)
	{
	}

	AnchorElement(const Element &elem)
		: __base_Element(elem)
	{
	}

	PROPERTY_BSTR(host)
	PROPERTY_BSTR(hostname)
	PROPERTY_BSTR(href)
	PROPERTY_BSTR(name)
	PROPERTY_READONLY_BSTR(nameProp)
	PROPERTY_BSTR(port)
	PROPERTY_BSTR(protocol)
	PROPERTY_BSTR(rel)
	PROPERTY_BSTR(target)

	operator CComPtr<IDispatch> () const
	{
		return p;
	}
};

/**
 * IHTMLInputHiddenElement wrapper
 */
class InputHiddenElement : public TElement<IHTMLInputHiddenElement>
{
public:
	InputHiddenElement()
	{
	}

	InputHiddenElement(IHTMLInputHiddenElement* pHidden)
		: __base_Element(pHidden)
	{
	}

	InputHiddenElement(IDispatch* pDispatch)
		: __base_Element(pDispatch)
	{
	}

	InputHiddenElement(const Element &elem)
		: __base_Element(elem)
	{
	}

	PROPERTY_BSTR(value)
};

/**
 * IHTMLInputFileElement wrapper
 */
class InputFileElement : public TElement<IHTMLInputFileElement>
{
public:
	InputFileElement()
	{
	}

	InputFileElement(IHTMLInputFileElement* pFile)
		: __base_Element(pFile)
	{
	}

	InputFileElement(const Element &elem)
		: __base_Element(elem)
	{
	}

	PROPERTY_bool(disabled)
//	PROPERTY_bool(status)
    PROPERTY_long(maxLength)
    PROPERTY_long(size)
    PROPERTY_BSTR(name)
    PROPERTY_BSTR(value)
    PROPERTY_JSEVENT(onchange)
    PROPERTY_JSEVENT(onselect)

	bool select()
	{
		ATLASSERT(p != NULL);
		return p && SUCCEEDED(p->select());
	}
};

/**
 * IHTMLScriptElement wrapper
 */
class ScriptElement : public TElement<IHTMLScriptElement>
{
private:
	INTERFACE_PROPERTY(IHTMLScriptElement2, Script2)

public:
	ScriptElement()
	{
	}

	ScriptElement(IHTMLScriptElement* pHTMLScript)
		: __base_Element(pHTMLScript)
	{
	}

	ScriptElement(const Element &elem)
		: __base_Element(elem)
	{
	}

    PROPERTY_bool(defer)
	PROPERTY_BSTR(event)
    PROPERTY_BSTR(htmlFor)
    PROPERTY_JSEVENT(onerror)
    //PROPERTY_BSTR(readyState)
    PROPERTY_BSTR(src)
    PROPERTY_BSTR(text)
    PROPERTY_BSTR(type)
    PROPERTY_BSTR_EX(Script2, charset)
};

/**
 * IHTMLElementCollection template wrapper
 */
template <class TElement = Element>
class Collection : public CComPtr<IHTMLElementCollection>
{
public:
	Collection()
	{
	}

	Collection(IHTMLElementCollection* pCollection)
		: CComPtr<IHTMLElementCollection>(pCollection)
	{
	}

	template <class TElem>
	Collection(const Collection<TElem> &collection)
		: CComPtr<IHTMLElementCollection>(collection.p)
	{
	}

	PROPERTY_long(length)
	__declspec(property(get = GetTags))		Collection	tags;
	__declspec(property(get = GetItem))		TElement	item[];

	Collection GetTags() const
	{
		ATLASSERT(p != NULL);

		return *this;
	}

	Collection GetTags(LPCOLESTR tagName) const
	{
		ATLASSERT(p != NULL);

		CComPtr<IDispatch> dispCollection;
		if (!p || FAILED(p->tags(CComVariant(tagName), &dispCollection)) || !dispCollection)
			return Collection();

		Collection collection;
		if (FAILED(dispCollection->QueryInterface(&collection)))
			return Collection();

		return collection;
	}

	TElement GetItem(long idx) const
	{
		ATLASSERT(p != NULL);

		CComPtr<IDispatch> dispElement;
		if (!p || FAILED(p->item(CComVariant(idx), CComVariant(idx), &dispElement)) || !dispElement)
			return TElement();

		TElement element;
		if (FAILED(dispElement->QueryInterface(&element)))
			return TElement();

		return element;
	}

	TElement operator[] (int idx) const
	{
		return GetItem(idx);
	}

public:
	class Iterator
	{
	private:
		long			m_index;
		TElement		m_current;
		Collection	m_collection;

	public:
		Iterator(const Collection &collection)
			: m_collection(collection)
			, m_index(0)
		{
			m_current = m_collection.item[m_index];
		}

		Iterator(const Iterator &it)
			: m_collection(it.m_collection)
			, m_index(0)
		{
			m_current = m_collection.item[m_index];
		}

		template <class TElem>
		Iterator(const Collection<TElem> &collection)
			: m_collection(collection)
			, m_index(0)
		{
			m_current = m_collection.item[m_index];
		}

		TElement &operator * ()
		{
			return m_current;
		}

		operator bool () const
		{
			return m_collection && m_index < m_collection.length;
		}

		bool operator ! () const
		{
			return !m_collection || m_index >= m_collection.length;
		}

		void operator ++ ()
		{
			m_current = m_collection.item[++m_index];
		}

		void operator ++ (int)
		{
			m_current = m_collection.item[++m_index];
		}

		void operator -- ()
		{
			m_current = m_collection.item[--m_index];
		}

		void operator -- (int)
		{
			m_current = m_collection.item[--m_index];
		}
	};

	Iterator GetIterator() const
	{
		return Iterator(*this);
	}
};

/**
 * Object with IDispatch properties
 */
template <class T>
class TDispatchProperties
{
public:
	__declspec(property(get = GetDispatchProperty)) CComPtr<IDispatch>	property[];
	__declspec(property(get = GetJSMethod))         JS::Method	        method[];
    
	CComPtr<IDispatch> GetDispatchProperty(LPOLESTR lpzPropertyName) const
	{
		IDispatch* p = (*(T*)this).p;

		DISPID dispID;
		if (!p || FAILED(p->GetIDsOfNames(IID_NULL, &lpzPropertyName, 1, LOCALE_SYSTEM_DEFAULT, &dispID)))
			return CComPtr<IDispatch>();

		DISPPARAMS dispparams;
		ZeroMemory(&dispparams, sizeof(dispparams));

		CComVariant vtRes;
		if (FAILED(p->Invoke(dispID,IID_NULL,LOCALE_SYSTEM_DEFAULT,DISPATCH_PROPERTYGET,&dispparams,&vtRes,NULL,NULL)))
			return CComPtr<IDispatch>();

		if (vtRes.vt != VT_DISPATCH || !vtRes.pdispVal)
			return CComPtr<IDispatch>();

		return vtRes.pdispVal;
	}

	JS::Method GetJSMethod(LPOLESTR lpzPropertyName) const
	{
		return GetDispatchProperty(lpzPropertyName);
	}
};

/**
 * IHTMLLocation wrapper
 */
class Location : public CComPtr<IHTMLLocation>
{
public:
	Location()
	{
	}

	Location(IHTMLLocation* pLocation)
		: CComPtr<IHTMLLocation>(pLocation)
	{
	}

	Location(const CComPtr<IDispatch> &dispLocation)
	{
		if (dispLocation) dispLocation->QueryInterface(&*this);
	}

	PROPERTY_BSTR(href)
};

/**
 * IHTMLWindow2 wrapper as template to use it in document
 */
template <typename TDocument>
class TWindow : public CComPtr<IHTMLWindow2>, public TDispatchProperties< TWindow<TDocument> >
{
private:
	INTERFACE_PROPERTY(IServiceProvider, ServiceProvider)

public:
	TWindow()
	{
	}

	TWindow(IHTMLWindow2* pWindow)
		: CComPtr<IHTMLWindow2>(pWindow)
	{
	}

	TWindow(const CComPtr<IDispatch> &dispDocument)
	{
		if (dispDocument) dispDocument->QueryInterface(&*this);
	}

	PROPERTY_BSTR(status);
	PROPERTY_BSTR(defaultStatus);
	PROPERTY_READONLY(TDocument, document)
	PROPERTY_READONLY(Event, event)
	PROPERTY_READONLY(TWindow, top)
	PROPERTY_READONLY(TWindow, self)
	PROPERTY_JSEVENT(onload)
	PROPERTY_JSEVENT(onunload)
	PROPERTY_JSEVENT(onerror)

    __declspec(property(get = GetHWND)) HWND hwnd;

	HRESULT navigate(const CComBSTR &bstrURL)
	{
		ATLASSERT(p != NULL);

		return p ? p->navigate(bstrURL) : E_FAIL;
	}

	HRESULT showModalDialog(BSTR dialog, VARIANT *varArgIn, VARIANT *varOptions, VARIANT *varArgOut)
	{
		ATLASSERT(p != NULL);

		return p ? p->showModalDialog(dialog,varArgIn,varOptions,varArgOut) : E_FAIL;
	}

	HRESULT showModelessDialog(BSTR url, VARIANT *varArgIn, VARIANT *options, IHTMLWindow2 **pDialog)
	{
		ATLASSERT(p != NULL);

		return p ? p->showModelessDialog(url, varArgIn, options, pDialog) : E_FAIL;
	}

    HWND GetHWND()
    {
        if (!ServiceProvider)
            return NULL;

        CComPtr<IWebBrowser2> browser;
        if (FAILED(ServiceProvider->QueryService(IID_IWebBrowserApp, IID_IWebBrowser2, (void**)&browser)))
            return NULL;

        SHANDLE_PTR hwnd = NULL;
        return SUCCEEDED(browser->get_HWND(&hwnd)) ? (HWND)hwnd : NULL;
    }
};

/**
 * IHTMLDocument2 wrapper
 */
class Document : public CComPtr<IHTMLDocument2>, public TDispatchProperties<Document>
{
private:
	INTERFACE_PROPERTY(IHTMLDocument3, Document3)

public:
	Document()
	{
	}

	Document(IHTMLDocument2* pDocument2)
		: CComPtr<IHTMLDocument2>(pDocument2)
	{
	}

	Document(const CComPtr<IDispatch> &dispDocument)
	{
		if (dispDocument) dispDocument->QueryInterface(&*this);
	}

	PROPERTY_READONLY(Collection<>, all)
	PROPERTY_READONLY(BodyElement,	body)
	PROPERTY_READONLY(Selection,	selection)
	PROPERTY_READONLY(Element,		activeElement)
	PROPERTY_READONLY(Location,		location)
	PROPERTY_READONLY(TWindow<Document>, parentWindow)
	PROPERTY_BSTR(cookie)
	PROPERTY_BSTR(title)
	
	__declspec(property(get = GetEvent))			Event			event;

	operator CComPtr<IDispatch> () const
	{
		return p;
	}
	
	Event GetEvent() const
	{
		ATLASSERT(p != NULL);

		CComPtr<IHTMLWindow2> window;
		if (!p || FAILED(p->get_parentWindow(&window)) || !window)
			return Event();
		
		Event event;
		if (FAILED(window->get_event(&event)))
			return Event();

		return event;
	}

	Element GetElementFromPoint(POINT pt)
	{
		return GetElementFromPoint(pt.x, pt.y);
	}

	Element GetElementFromPoint(long x, long y)
	{
		ATLASSERT(p != NULL);

		Element element;
		if (!p || FAILED(p->elementFromPoint(x, y, &element)))
			return Element();

		return element;
	}

	Element getElementById(const CComBSTR &id) const
	{
		Element element;
		if (!Document3 || FAILED(Document3->getElementById(id, &element)))
			return Element();

		return element;
	}

	template <class T>
	bool getElementById(const CComBSTR &id, CComPtr<T> &element) const
	{
		CComPtr<IHTMLElement> htmlElement;
		if (!Document3 || FAILED(Document3->getElementById(id, &htmlElement)) || !htmlElement)
			return false;

		return SUCCEEDED(htmlElement->QueryInterface(&element)) && element;
	}

	template <class T>
	Collection<T> getElementsByTagName(const CComBSTR &bsName) const
	{
		Collection<T> result;
		if (!Document3 || FAILED(Document3->getElementsByTagName(bsName, &result)))
			return Collection<T>();

		return result;
	}

	template <class T>
	Collection<T> getElementsByName(const CComBSTR &bsName) const
	{
		Collection<T> result;
		if (!Document3 || FAILED(Document3->getElementsByName(bsName, &result)))
			return Collection<T>();

		return result;
	}

	Element createElement(const CComBSTR &eTag)
	{
		ATLASSERT(p != NULL);

		Element element;
		if (!p || FAILED(p->createElement(eTag, &element)))
			return Element();

		return element;
	}

	bool execCommand(const CComBSTR &cmdID, VARIANT value = CComVariant(), bool showUI = false)
	{
		ATLASSERT(p != NULL);

		VARIANT_BOOL vbResult = VARIANT_FALSE;
		if (!p || FAILED(p->execCommand(cmdID, ToVariant(showUI), value, &vbResult)))
			return false;

		return VARIANT_TRUE == vbResult;
	}

	bool write(SAFEARRAY *psarray)
	{
		ATLASSERT(p != NULL);
		ATLASSERT(psarray != NULL);
		return p && SUCCEEDED(p->write(psarray));
	}

	bool write(const CComBSTR &html)
	{
		ATLASSERT(html);
		ATLASSERT(p != NULL);

		CComSafeArray<VARIANT> safeHTML(1);
		safeHTML[0] = html;
		return write(safeHTML);
	}

	bool write(LPCSTR pszHTML)
	{
		return write(CComBSTR(pszHTML));
	}

	bool write(LPCWSTR pszHTML)
	{
		return write(CComBSTR(pszHTML));
	}

	Element createHiddenDiv(const CComBSTR &bsInnerHTML)
	{
		Element div = createElement(L"div");
		if (!div) return Element();

		if (!(div.style.visibility = L"hidden"))
			return Element();

		if (!(div.style.position = L"absolute"))
			return Element();

		if (!(div.innerHTML = bsInnerHTML))
			return Element();

		if (!body.appendChild(div))
			return Element();

		return div;
	}
};

typedef TWindow<Document> Window;

/**
 * IHTMLFrameBase wrapper
 */
class Frame : public TElement<IHTMLFrameBase>
{
private:
	INTERFACE_PROPERTY(IHTMLFrameBase2, FrameBase2)

public:
	Frame()
	{
	}

	Frame(IHTMLFrameBase* pHTMLFrame)
		: __base_Element(pHTMLFrame)
	{
	}

	Frame(Element htmlElement)
		: __base_Element(htmlElement)
	{
	}

	Frame(const CComPtr<IDispatch> &dispElem)
	{
		if (dispElem) dispElem->QueryInterface(&*this);
	}

	PROPERTY_BSTR(src)
	PROPERTY_BSTR_VARIANT(border)
	PROPERTY_BSTR(frameBorder)
	PROPERTY_BSTR(scrolling)
	PROPERTY_READONLY_EX(FrameBase2, Window, contentWindow)
};

}; //namespace HTML

/**
 * WEB Browser wrapper
 */
class WEBBrowser : public CComPtr<IWebBrowser2>
{
public:
	WEBBrowser(void)
	{
	}

	WEBBrowser(IWebBrowser2 *pBrowser)
		: CComPtr<IWebBrowser2>(pBrowser)
	{
	}

	WEBBrowser(IDispatch *pBrowser)
	{
		if (pBrowser) pBrowser->QueryInterface(&*this);
	}

	~WEBBrowser()
	{
		Release();
	}

	// Release the interface and set to NULL
	void Release(bool sendClose = true) throw()
	{
		if (!p)
			return;

		// Saving HWND
		HWND hWnd = hwnd;
		bool bVisible = Visible;

		IWebBrowser2* pTemp = p;
		p = NULL;

		// Release
		ULONG nRefCount = pTemp->Release();
		// check if window exists than IE7 browser is not freed
		if (sendClose && !nRefCount && hWnd && !bVisible && ::IsWindow(hWnd))
			::PostMessage(hWnd, WM_SYSCOMMAND, SC_CLOSE, 0);
	}

	static void MagicRelease(CComPtr<IUnknown> &ptr)
	{
		if (!ptr) 
			return;

		WEBBrowser browser;
		if (SUCCEEDED(ptr->QueryInterface(&browser)))
			ptr.Release();
	}

	IWebBrowser2* operator = (IWebBrowser2* pBrowser)
	{
		return CComPtr<IWebBrowser2>::operator = (pBrowser);
	}

	CLSID GetCLSID() const
	{
		CLSID clsidIE = __uuidof(0);
		if (SUCCEEDED(CLSIDFromProgID(L"InternetExplorer.Application", &clsidIE)))
			return clsidIE;

#ifdef _USE_LOGGING
		CLogger logger;
		LOG("WEBBrowser FAILED to get InternetExplorer.Application CLSID, CLSID_InternetExplorer will be used");
#endif

		return CLSID_InternetExplorer;
	}

	HRESULT CreateInstance(DWORD dwClsContext = CLSCTX_ALL)
	{
		return CoCreateInstance(GetCLSID(), NULL, dwClsContext);
	}

	HRESULT CreateNewInstance()
	{
#ifdef _USE_LOGGING
		CLogger logger;
#endif
        LOG("WEBBrowser::Create...");

		CComPtr<IClassFactory> factory;

		HRESULT hr = ::CoGetClassObject(GetCLSID(), CLSCTX_ALL, NULL, __uuidof(IClassFactory), (void**)&factory);
		if (FAILED(hr))
		{
			LOG("WEBBrowser::Create: FAILED to get IClassFactory");
			if (!IsIE() && (hr == HRESULT_FROM_WIN32(RPC_S_SERVER_UNAVAILABLE)))
			{
				if (FAILED(StartIE()))
					return hr;

				LOG("WEBBrowser::Try to create IClassFactory");
				for (int i = 0; i < 7; ++i)
				{
					if (SUCCEEDED(hr = ::CoGetClassObject(GetCLSID(), CLSCTX_ALL, NULL, __uuidof(IClassFactory), (void**)&factory)))
						break;
					// wait for start
					Sleep(1000);
				}
			}

			if (FAILED(hr))
				return hr;
		}

		if (FAILED(factory->CreateInstance(NULL, __uuidof(IWebBrowser2), (void**)&*this)))
		{
			LOG("WEBBrowser::Create: FAILED to Create Instance");
			return E_FAIL;
		}

		LOG("WEBBrowser::Create: Browser has been created successfully");

		return S_OK;
	}

	PROPERTY_bool(Visible)
	PROPERTY_READONLY_BSTR(LocationURL)
    __declspec(property(get = GetDocument)) HTML::Document document;
	__declspec(property(get = GetEvent))	HTML::Event	   event;
	__declspec(property(get = GetHWND))		HWND		   hwnd;
	PROPERTY_long(Left)
	PROPERTY_long(Top)
	PROPERTY_long(Width)
	PROPERTY_long(Height)
	PROPERTY_bool(AddressBar)
	PROPERTY_bool(MenuBar)
	PROPERTY_bool(Resizable)
	PROPERTY_bool(StatusBar)
	PROPERTY_bool(TheaterMode)
	PROPERTY_bool(FullScreen)
	PROPERTY_int(ToolBar)
	
	HTML::Document GetDocument() const
	{
		ATLASSERT(p != NULL);

		CComPtr<IDispatch> dispDocument;
		if (!p || FAILED(p->get_Document(&dispDocument)) || !dispDocument)
			return HTML::Document();

		return HTML::Document(dispDocument);
	}

	HTML::Event GetEvent() const
	{
		return GetDocument().GetEvent();
	}

	HRESULT Navigate(UINT uURL, bool bNewTab = false)
	{
		CComBSTR bstrURL;
		if (!bstrURL.LoadString(uURL))
			return E_FAIL;

		return Navigate(bstrURL, bNewTab);
	}

	HRESULT Navigate(const CString &strURL, bool bNewTab = false)
	{
		return Navigate(CComBSTR(strURL), bNewTab);
	}

	HRESULT Navigate(const CComBSTR &bstrURL, bool bNewTab = false)
	{
		ATLASSERT(p != NULL);

		if (!p)
			return E_FAIL;

		CComVariant vHeaders, vPostData;

		return Navigate(bstrURL, vPostData, vHeaders, bNewTab);
	}

	HRESULT PostData(const CComBSTR &bstrURL, CComVariant &vPostData, CComVariant &vHeaders, bool bNewTab = false)
	{
		ATLASSERT(p != NULL);

		return Navigate(bstrURL, vPostData, vHeaders, bNewTab);
	}

	HRESULT PostData(const CString &strURL, const CStringA &strPostData, const CString &strHeaders, bool bNewTab = false)
	{
		ATLASSERT(p != NULL);

		CComVariant vtPostData;
		if (FAILED(StringToPostDataVariant(strPostData, vtPostData)))
			return E_FAIL;

		return PostData(CComBSTR(strURL), vtPostData, CComVariant(strHeaders), bNewTab);
	}

	HWND GetHWND() const
	{
		ATLASSERT(p != NULL);

		SHANDLE_PTR hwnd = NULL;
		if (!p || FAILED(p->get_HWND(&hwnd)))
			return NULL;

		return (HWND)hwnd;
	}
#ifdef __ATLWIN_H__
	__declspec(property(get = GetWindow)) CWindow wnd;
	CWindow GetWindow() const
	{
        return GetHWND();
	}
#endif

	void Stop() const
	{
		ATLASSERT(p != NULL);
		if (p) p->Stop();
	}

	void GoHome() const
	{
		ATLASSERT(p != NULL);
		if (p) p->GoHome();
	}

	HRESULT Refresh()
	{
		ATLASSERT(p != NULL);
		return p ? p->Refresh() : E_FAIL;
	}

	HRESULT Refresh2(RefreshConstants level)
	{
		ATLASSERT(p != NULL);
		return p ? p->Refresh2(&CComVariant(level)) : E_FAIL;
	}

	HRESULT ShowBrowserBar(const CComVariant &clsid, bool bShow) const
	{
		ATLASSERT(p != NULL);
		CComVariant vtShow(bShow), vtEmpty;
		return p ? p->ShowBrowserBar((VARIANT*)&clsid, &vtShow, &vtEmpty) : E_FAIL;
	}

	void HideAllBars()
	{
		AddressBar = false;
		MenuBar = false;
		StatusBar = false;
		ToolBar = FALSE;
	}

public:
	static WEBBrowser NewInstance()
	{
		WEBBrowser browser;
		browser.CreateNewInstance();
		return browser;
	}

private:
	HRESULT StartIE()
	{
#ifdef _USE_LOGGING
		CLogger logger;
#endif

		LOG("WEBBrowser::Try to start IE instance");

		STARTUPINFO si;
		PROCESS_INFORMATION pi;

		ZeroMemory( &si, sizeof(si) );
		si.cb = sizeof(si);
		si.dwFlags = STARTF_USESHOWWINDOW;
		si.wShowWindow = 0;
		ZeroMemory( &pi, sizeof(pi) );

		CString strPath;
		HRESULT hResult = ::SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL, SHGFP_TYPE_CURRENT, strPath.GetBuffer(MAX_PATH));
		strPath.ReleaseBuffer();

		strPath += _T("\\Internet Explorer\\IEXPLORE.EXE about:blank");

		// Start the child process. 
		if(!CreateProcess(
			NULL,			  // No module name (use command line). 
			(LPTSTR)(LPCTSTR)strPath, // Command line. 
			NULL,             // Process handle not inheritable. 
			NULL,             // Thread handle not inheritable. 
			FALSE,            // Set handle inheritance to FALSE. 
			0,                // No creation flags. 
			NULL,             // Use parent's environment block. 
			NULL,             // Use parent's starting directory. 
			&si,              // Pointer to STARTUPINFO structure.
			&pi)              // Pointer to PROCESS_INFORMATION structure.
			) 
		{
			LOG("WEBBrowser::IE process creation failed");
			return E_FAIL;
		}

		// Wait 3 sec.
		WaitForSingleObject(pi.hProcess, 2 * 1000);

		// Close process and thread handles. 
		if (pi.hProcess) CloseHandle(pi.hProcess);
		if (pi.hThread) CloseHandle(pi.hThread);

		return S_OK;
	}

	HRESULT StringToPostDataVariant(const CStringA &strPostData, CComVariant &vtPostData) const
	{
		LPSAFEARRAY psa = SafeArrayCreateVector(VT_UI1, 0, strPostData.GetLength());
		if (!psa) return E_OUTOFMEMORY;

		HRESULT hr;
		void* pData = NULL;
		if (FAILED(hr = SafeArrayAccessData(psa, &pData)))
			return hr;

		CopyMemory(pData, (LPCSTR)strPostData, strPostData.GetLength());
		hr = SafeArrayUnaccessData(psa);

		vtPostData.vt = VT_ARRAY | VT_UI1;
		vtPostData.parray = psa;

		return hr;
	}

	HRESULT Navigate(const CComBSTR &bstrURL, CComVariant &vPostData, CComVariant &vHeaders, bool bNewTab)
	{
		ATLASSERT(p != NULL);

		if (!p)
			return E_FAIL;

		CComVariant vFlags, vTargetFrameName;

		if (!bNewTab || !IsIE7OrHigher())
			return p->Navigate(bstrURL, &vFlags, &vTargetFrameName, &vPostData, &vHeaders);
		else
		{
			CComVariant vFlagsEx; 
			vFlagsEx.vt = VT_I4;
			vFlagsEx.lVal = 0x800; //navOpenInNewTab

			if (SUCCEEDED(p->Navigate(bstrURL, &vFlagsEx, &vTargetFrameName, &vPostData, &vHeaders)))
				return S_OK;

			// may be tabs are disabled
			return p->Navigate(bstrURL, &vFlags, &vTargetFrameName, &vPostData, &vHeaders);
		}
	}
};

}; //namespace IE
}; //namespace Automation
